package org.osite.xml;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

import org.osite.util.Preconditions;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class DefaultMemberHandler<T> implements MemberHandler<T> {
    private final Method setter;
    private final Class<?> paramType;
    private final AttributeConverter<?> convertor;
    private final ArgumentsHelper argsHelper;

    public DefaultMemberHandler(Method setter, ConvertorProvider provider)
            throws ParseSetupException {
        this.setter = Preconditions.checkNotNull(setter);
        Class<?> types[] = setter.getParameterTypes();
        paramType = types[0];

        // optional
        convertor = provider.getAttributeConverter(types[0]);
        argsHelper = new ArgumentsHelper(setter, provider, 1);
    }

    @SuppressWarnings("unchecked")
    private Object getMemberObject(Element element, ParseContext context)
            throws XmlParseException {
        NodeList list = element.getChildNodes();
        if (list.getLength() == 1 && convertor != null
                && list.item(0).getNodeType() == Node.TEXT_NODE) {
            return new Object[] {convertor.convert(
                    paramType, list.item(0).getNodeValue(), context)};
        }
        ArrayList<Object> result = new ArrayList<Object>();
        for (int i = 0, n = list.getLength(); i < n; ++i) {
            Node node = list.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE) continue;
            result.add(context.parseEntity((Element) node));
        }
        if (!paramType.isArray()) {
            if (result.size() != 1) {
                throw new XmlParseException(
                        "there should be only one child elements: "
                        + result.size(), element);
            }
            return result.get(0);
        }

        return result.toArray((T[]) Array.newInstance(
                paramType.getComponentType(), result.size()));
    }


    @Override
    public void processByName(T component, Element element, ParseContext context)
            throws XmlParseException {
        try {
            Object[] args = argsHelper.getArguments(element, context);
            args[0] = getMemberObject(element, context);
            setter.invoke(component, args);
        } catch (IllegalArgumentException e) {
            throw new XmlParseException(
                    "fail to invoke setters of child: ", element);
        } catch (IllegalAccessException e) {
            throw new XmlParseException(
                    "fail to invoke setters of child: ", element);
        } catch (InvocationTargetException e) {
            throw new XmlParseException(
                    "fail to invoke setters of child: ", element);
        }
    }

    @Override
    public void processByType(T component, Element element, ParseContext context)
            throws XmlParseException {
        try {
            Object[] args = argsHelper.getArguments(element, context);
            args[0] = context.parseEntity(element);
            setter.invoke(component, args);
        } catch (IllegalArgumentException e) {
            throw new XmlParseException(
                    "fail to invoke setters of child: ", element);
        } catch (IllegalAccessException e) {
            throw new XmlParseException(
                    "fail to invoke setters of child: ", element);
        } catch (InvocationTargetException e) {
            throw new XmlParseException(
                    "fail to invoke setters of child: ", element);
        }
    }

    @Override
    public String toString() {
        return "DefaultMemberHandler: "
                + setter.getDeclaringClass().getSimpleName()
                + "#" + setter.getName()
                + " [id:" + System.identityHashCode(this) +"]";
    }
}
